﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using IEx.Common;
using IEx.Common.Sources;
using IEx.Downloader;
using IEx.Model;
using IEx.Model.Partials;
using IEx.Utilities;

namespace IEx
{
    public class DataFeed : IDisposable
    {
        private static Setting setting;
        // Share between tasks
        private static RequestQueue requestQueue;
        private static DownloadQueue downloadQueue;
        private static DownloadQueue failDownloadQueue;
        //private static List<SourceItem> todaySourceItems;
        private static List<Pressrelease> lstPressrelease;

        private RequestProcess requestProcess;
        private DownloadProcess downloadProcess;
        private readonly Timer timer;
        private DownloadProcess processFailDownload;

        private static DateTime LatestSessionDate = DateTime.MinValue;

        private const int CRAWL_INTERVAL = 1; // Time interval (in minute)
        private static bool flagFirstScanning = false;

        private bool IsCompleteAllProcesses = true;
        private bool IsUpdateMode = false;

        ~DataFeed()
        {
            Dispose();
        }

        static DataFeed()
        {
            //ProtocolProviderFactory.RegisterProtocolHandler("http", typeof(HttpProtocolProvider));
            //ProtocolProviderFactory.RegisterProtocolHandler("https", typeof(HttpProtocolProvider));
            //ProtocolProviderFactory.RegisterProtocolHandler("ftp", typeof(HttpProtocolProvider));

            setting = new Setting();
            requestQueue = new RequestQueue();
            downloadQueue = new DownloadQueue();
            failDownloadQueue = new DownloadQueue();
            //todaySourceItems = new List<SourceItem>();
            lstPressrelease = new List<Pressrelease>();
        }

        #region Constructors
        public DataFeed()
            : this(false, null, null, null, null)
        {
        }


        /// <summary>
        /// Create an instance of AppFeed to update/reload the pressrelease with Custom Source
        /// </summary>
        /// <param name="isCustom"></param>
        /// <param name="isUpdated"></param>
        /// <param name="sourceCode"></param>
        /// <param name="languageId"></param>
        /// <param name="dateFrom"></param>
        /// <param name="dateTo"></param>
        public DataFeed(bool isUpdated, string sourceCode = null, int? languageId = null, DateTime? dateFrom = null, DateTime? dateTo = null)
        {
            IsUpdateMode = isUpdated;

            requestProcess = new RequestProcess(requestQueue, downloadQueue);
            downloadProcess = new DownloadProcess(downloadQueue, new DefaultStore());
            processFailDownload = new DownloadProcess(failDownloadQueue, new DefaultStore());

            requestProcess.OnFinish += new FinishEventHandler(requestProcess_OnFinish);
            processFailDownload.OnFinish += new FinishEventHandler(processFailDownload_OnFinish);
            downloadProcess.OnFinish += new FinishEventHandler(downloadProcess_OnFinish);


            if (IsUpdateMode)
            {
                this.TimerTicked(sourceCode, languageId, dateFrom, dateTo);
            }
            else
            {
                TimerTicked();
                /*this.timer = new Timer(
                    (obj) => TimerTicked(),
                    null,
                    TimeSpan.Zero,
                    TimeSpan.FromMinutes(CRAWL_INTERVAL) // Start crawler after each 30 minutes of time
                );*/
            }
        }

        public DataFeed(string sourceCode, Nullable<int> lang, DateTime fromDate, DateTime toDate)
        {
            DateTime endDate = toDate;
            string pdfFolderPath = DataFeedConfiguration.GetConfig().DownloadedFolder.Path;
            Repository prRes = new Repository();
            do
            {
                //update content for pr from to.AddMonths(-1) -> to
                List<Attachment> lstAttachments = prRes.GetAttachmentForPrMissingContent(sourceCode, lang, endDate.AddMonths(-1), endDate);
                lstAttachments.ForEach((at) =>
                {
                    string pdfFile = Path.Combine(pdfFolderPath, at.Location);
                    string message = Utility.ReadPdfFile(pdfFile);
                    if (!string.IsNullOrEmpty(message))
                        prRes.UpdatePressreleasesContent(at.PressreleaseId, message);
                    Logger.Debug(string.Format("PressReleaseID {0}: {1}", at.PressreleaseId, pdfFile));
                });
                endDate = endDate.AddMonths(-1);
            } while (endDate >= fromDate);
        }

        #endregion

        void downloadProcess_OnFinish(FinishEventArg arg)
        {
            Logger.WriteLogInfo(string.Format("Download completely ({0:MM/dd/yyyy HH:mm:ss}), Total({1}) Complete({2})", DateTime.Now, arg.Total, arg.Complete));
            // Mark all processes are complete and ready to start a new scanning
            this.IsCompleteAllProcesses = true;

            // Check for the any errors (exceptions) if have to send an email
            // after complete one scaning interval
            Logger.SendErrorEmail();
            if (!IsUpdateMode)
                Logger.Debug("Waiting for the next scanning...");
            else
                Environment.Exit(1);
        }

        void processFailDownload_OnFinish(FinishEventArg arg)
        {
            Logger.WriteLogInfo(string.Format("Download completely all previous fail downloads ({0:MM/dd/yyyy HH:mm:ss}), Total({1}) Complete({2})", DateTime.Now, arg.Total, arg.Complete));
            downloadProcess.Start();
        }

        void requestProcess_OnFinish(FinishEventArg arg)
        {
            Logger.WriteLogInfo(string.Format("Completely all scannings ({0:MM/dd/yyyy HH:mm:ss}), Total({1}) Complete({2})", DateTime.Now, arg.Total, arg.Complete));
            Logger.WriteLogInfo(string.Format("*** START DOWLOADING ({0:MM/dd/yyyy HH:mm:ss}) ***", DateTime.Now));

            processFailDownload.Start();
        }

        /// <summary>
        /// Load the requests from soureconfig.xml, but, load daily request for Shanghai & Hongkong markets as well
        /// </summary>
        /// <param name="companycode">Company's code to filter. Used only for Update mode</param>
        /// <param name="languageId">Id of language to filter. Used only for Update mode</param>
        /// <param name="dateFrom">Start of date to filter. Used only for Update mode</param>
        /// <param name="dateTo">End of date to filter. Used only for Update mode</param>
        /// <note>[04-Sep-2014 binh.nguyen]</note>
        private void QueueRequestItem(string sourceCode, int? languageId, DateTime? dateFrom, DateTime? dateTo)
        {
            try
            {
                IEnumerable<SourceItem> sources = setting.LoadAll();

                if (!IsUpdateMode)
                {
                    #region Normal mode

                    List<SourceItem> failRequests = Utility.LoadFailRequests();
                    List<Announcement> faildAnnouncements = Utility.LoadFailAnnouncement();

                    // Only Hongkong market has daily url for these markets and need to scan daily urls
                    // instead of historical urls. 
                    // Rule: scan daily url only 1 times per day
                    if (sources.Any(s => s.SourceId == (int)SourceIds.HKExNews) && flagFirstScanning)
                    {

                        /*IEnumerable<SourceItem> dailySourceItems = setting.GetLatestTickerSourceItem();
                        foreach (SourceItem source in dailySourceItems)
                        {
                            if (!requestQueue.Contains(source))
                                requestQueue.Enqueue(source);
                        }*/
                        // Reload sourcemap.xml with new source items
                        sources = setting.LoadAll();
                    }

                    // Add requests which were failed in last scanning.
                    foreach (SourceItem failRequest in failRequests)
                    {
                        if (!requestQueue.Contains(failRequest))
                            requestQueue.Enqueue(failRequest);
                    }

                    foreach (SourceItem source in sources)
                    {
                        // Run app at the first time
                        if (flagFirstScanning)
                        {
                            if (!requestQueue.Contains(source))
                                requestQueue.Enqueue(source);
                        }
                        else
                        {
                            bool isExist = !string.IsNullOrEmpty(source.DailyUrl) &&
                                       lstPressrelease.Any(q => q.SourceCode == source.SourceCode
                                           && q.LanguageId == source.LangId
                                           && q.SourceId == source.SourceId
                                       );
                            // Scan daily url instead of historical url
                            if (isExist)
                            {
                                source.Url = source.DailyUrl;
                            }

                            if (!requestQueue.Contains(source))
                                requestQueue.Enqueue(source);
                        }
                    }

                    if (flagFirstScanning) flagFirstScanning = false;

                    // Add attachments which were downloaded failed in last scanning
                    foreach (Announcement announcement in faildAnnouncements)
                    {
                        failDownloadQueue.Enqueue(announcement);
                    }

                    // write log
                    Logger.WriteLogInfo(string.Format(
                        @"Start Scaning: Requests ({0}), Failured Requests ({1}), Failured Dowloads ({2})",
                        requestQueue.QueueCount, failRequests.Count, failDownloadQueue.QueueCount)
                    );

                    #endregion /Normal mode
                }
                else
                {
                    #region Update mode

                    string filteredSourceCode = "";

                    foreach (SourceItem src in sources)
                    {
                        bool satisfaction = false;
                        if (!string.IsNullOrEmpty(sourceCode)
                            && languageId > 0
                            && src.SearchCode.Equals(sourceCode, StringComparison.OrdinalIgnoreCase)
                            && src.LangId == languageId)
                        {
                            satisfaction = true;
                            filteredSourceCode = sourceCode;
                        }
                        else
                        {
                            if (string.IsNullOrEmpty(sourceCode) && (languageId.HasValue && languageId > 0 && languageId == src.LangId))
                            {
                                filteredSourceCode = src.SearchCode;
                                satisfaction = true;
                            }
                            else if (!string.IsNullOrEmpty(sourceCode) && src.SearchCode.Equals(sourceCode, StringComparison.OrdinalIgnoreCase))
                            {
                                satisfaction = true;
                                filteredSourceCode = sourceCode;
                            }
                            else if (string.IsNullOrEmpty(sourceCode) && !languageId.HasValue)
                            {
                                filteredSourceCode = src.SearchCode;
                                satisfaction = true;
                            }
                        }

                        if (satisfaction)
                        {
                            SourceItem newSourceItem = new SourceItemUpdate()
                            {
                                Url = src.Url,
                                LangId = src.LangId,
                                FilterLanguageID = languageId,
                                SourceCode = filteredSourceCode,
                                SourceId = src.SourceId,
                                SearchCode = src.SearchCode,
                                Category = src.Category,
                                DateTimeFormat = src.DateTimeFormat,
                                DailyUrl = src.DailyUrl,
                                FailId = src.FailId,
                                DateFrom = dateFrom,
                                DateTo = dateTo
                            };

                            requestQueue.Enqueue(newSourceItem);
                        }
                    }

                    // write log
                    Logger.WriteLogInfo(string.Format(
                        @"Start Scaning: Requests ({0}), Failured Dowloads ({1})",
                        requestQueue.QueueCount, failDownloadQueue.QueueCount)
                    );
                    #endregion /Update mode
                }


            }
            catch (XmlException ex)
            {
                throw new XmlException("There's an error inside the sourceconfig.xml. Sourceconfig is not well-form.", ex);
            }
        }

        private void TimerTicked()
        {
            this.TimerTicked(null, null, null, null);
        }

        private void TimerTicked(string sourceCode, int? languageId, DateTime? dateFrom, DateTime? dateTo)
        {
            try
            {
                // Wait for all other processes are complete
                if (!this.IsCompleteAllProcesses)
                    return;

                this.IsCompleteAllProcesses = false;
                string startLogMsg = string.Format("*** START SCANNING ({0:MM/dd/yyyy HH:mm:ss}) ***", DateTime.Now);
                // write log
                Logger.WriteLogInfo(startLogMsg);

                //PressreleasesRepository pl = new PressreleasesRepository();
                //lstPressrelease = pl.GetCompanyToAlert();

                // update source map and send daily report at the first time scanning of each new day
                DailyCheck();

                // queue item and start scanning
                QueueRequestItem(sourceCode, languageId, dateFrom, dateTo);

                // update ignore list
                Utility.IgnoreAnnouncements = Utility.IgnoreAnnouncementTitles();
                requestProcess.Start();

            }
            catch (Exception ex)
            {
                Logger.WriteError("An error occured in TimerTicked(): ", ex);
            }
        }

        private static void DailyCheck()
        {
            if (DateTime.Now.ToString("yyyyMMdd") != LatestSessionDate.ToString("yyyyMMdd"))
            {
                flagFirstScanning = true;
                // Send daily report email
                try
                {
#if !DEBUG
                    //Utility.DailyReport(lstPressrelease.FindAll(q => q.IsAlert == 1));
#endif
                }
                catch (Exception ex)
                {
                    Logger.WriteError("Error while sending daily report", ex);
                }

                LatestSessionDate = DateTime.Now;
            }
        }

        public void Dispose()
        {
            requestQueue.ClearQueue();
            downloadQueue.ClearQueue();
            requestQueue = null;
            downloadQueue = null;
            requestProcess = null;
            downloadProcess = null;
            Logger.ClearLogger();
            if (timer != null)
                timer.Dispose();
        }
    }
}
